﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using Microsoft.Expression.Encoder;
using System.ComponentModel;
using Microsoft.Expression.Encoder.Profiles;
using System.Runtime.Remoting.Messaging;


namespace VideoEncoder
{
    public delegate void ErrorEventHandler(object sender, string message);
    public class MediaEncoder : INotifyPropertyChanged
    {
        public event ErrorEventHandler OnError;
        public event EventHandler OnEncodeCompleted;
        public event EventHandler OnProgressChanged;

        private MediaItem mediaItem;        
        private bool cancelEncode;
        private Thread encodeThread;
        private FileInfo file;        
        private DateTime start, pauseTime;
        private DateTime? endTime, startTime;
        private ItemStatus status;
        private string name;
        private string fileDuration;
        private string originalVideoSize;
        private string originalFrameRate;
        private long fileLength;
        private double progress;
        private string progressText;
        private int pass;

        public FileInfo File
        {
            get { return file; }
            set
            {
                file = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("File"));
            }
        }
        public DateTime? EndTime
        {
            get { return endTime; }
            set
            {
                endTime = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("EndTime"));
            }
        }
        public DateTime? StartTime
        {
            get { return startTime; }
            set
            {
                startTime = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("StartTime"));
            }
        }
        public MediaItem MediaItem
        {
            get { return mediaItem; }
            set
            {
                mediaItem = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("MediaItem"));
            }
        }
        public ItemStatus Status
        {
            get { return status; }
            set
            {
                status = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Status"));
            }
        }
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Name"));
            }
        }
        public string FileDuration
        {
            get { return fileDuration; }
            set
            {
                fileDuration = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("FileDuration"));
            }
        }
        public string OriginalVideoSize
        {
            get { return originalVideoSize; }
            set
            {
                originalVideoSize = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("OriginalVideoSize"));
            }
        }
        public string OriginalFrameRate
        {
            get { return originalFrameRate; }
            set
            {
                originalFrameRate = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("OriginalFrameRate"));
            }
        }
        public long FileLength
        {
            get { return fileLength; }
            set
            {
                fileLength = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("FileLength"));
            }
        }
        public double Progress
        {
            get { return progress; }
            set
            {
                progress = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Progress"));
            }
        }
        public string ProgressText
        {
            get { return progressText; }
            set
            {
                progressText = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("ProgressText"));
            }
        }
        public bool IsCanceled
        {
            get { return cancelEncode; }
        }
        public string TargetFile
        {
            get { return System.IO.Path.Combine(OutputDirectory, System.IO.Path.GetFileNameWithoutExtension(File.Name) + ".wmv"); }
        }
        public bool DeleteFile { get; set; }

        VideoProfile vProfile;
        AudioProfile aProfile;
        VideoComplexity vComp;
        Job job;


        public string OutputDirectory { get; set; }

        public MediaEncoder(string fileToEncode, VideoProfile videoProfile, AudioProfile audioProfile, VideoComplexity videoComplexity)
        {            
            file = new FileInfo(fileToEncode);
            name = Path.GetFileNameWithoutExtension(fileToEncode);
            FileLength = file.Length;
            Status = ItemStatus.Analyzing;
            vProfile = videoProfile;
            aProfile = audioProfile;
            vComp = videoComplexity;
            job = new Job();
            LoadMediaItemDel del = new LoadMediaItemDel(LoadMediaItem);
            del.BeginInvoke(new AsyncCallback(LoadMediaItemCallBack), null);
        }

        delegate void LoadMediaItemDel();
        private void LoadMediaItem()
        {
            try
            {
                MediaItem = new MediaItem(file.FullName);
                mediaItem.PropertyChanged += new PropertyChangedEventHandler(mediaItem_PropertyChanged);
                
                mediaItem.VideoProfile = vProfile;
                mediaItem.AudioProfile = aProfile;
                            
                
                mediaItem.VideoComplexity = vComp;
                Status = mediaItem.Status;
                OriginalFrameRate = string.Format("{0:#.###}", mediaItem.OriginalFrameRate);
                OriginalVideoSize = string.Format("{0}x{1}", mediaItem.OriginalVideoSize.Width, mediaItem.OriginalVideoSize.Height);
                FileDuration = string.Format("{0:00}:{1:00}:{2:00}", mediaItem.FileDuration.Hours, mediaItem.FileDuration.Minutes, mediaItem.FileDuration.Seconds);

                job.MediaItems.Add(mediaItem);
                               
            }
            catch (InvalidMediaFileException exp)
            {
                if (OnError != null)
                    OnError(this,exp.Message);                
            }
        }
        void LoadMediaItemCallBack(IAsyncResult ar)
        {
            LoadMediaItemDel worker = (LoadMediaItemDel)((AsyncResult)ar).AsyncDelegate;
            worker.EndInvoke(ar);
        }

        void mediaItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            mediaItem = sender as MediaItem;
            switch (e.PropertyName)
            {
                case "FileDuration":
                    FileDuration = string.Format("{0}:{1}:{2}", mediaItem.FileDuration.Hours, mediaItem.FileDuration.Minutes, mediaItem.FileDuration.Seconds);
                    break;
                case "OriginalVideoSize":
                    OriginalVideoSize = string.Format("{0}x{1}", mediaItem.OriginalVideoSize.Width, mediaItem.OriginalVideoSize.Height);
                    break;
                case "OriginalFrameRate":
                    OriginalFrameRate = string.Format("{0:#.###}", mediaItem.OriginalFrameRate);
                    break;
                case "Status":
                    Status = mediaItem.Status;
                    break;
                default:
                    break;
            }
        }

        public void Encode()
        {
            if (mediaItem == null)
            {
                //IndicateEncodingIsFinished();
                return;
            }

            // Indicate that we're not cancelling.
            cancelEncode = false;

            // Create a thread to do the encode.
            encodeThread = new Thread(new ThreadStart(EncodeThread));
            encodeThread.Start();
        }

        private void EncodeThread()
        {
            try
            {                   
                job.OutputDirectory = OutputDirectory;
                job.CreateSubfolder = false;

                job.EncodeProgress += new EventHandler<EncodeProgressEventArgs>(job_EncodeProgress);
                job.StartFileEncoding += new EventHandler<StartFileEncodingEventArgs>(job_StartFileEncoding);

                pass = 0;
                EndTime = null;
                StartTime = DateTime.Now;
                job.Encode();
                EndTime = DateTime.Now;
                if (!cancelEncode)
                {
                    if (DeleteFile)
                        File.Delete();
                    Status = ItemStatus.FinishedEncode;
                }   
            }
            catch (Exception ex)
            {
                Status = ItemStatus.Error;
                if (OnError != null)
                    OnError(this,ex.Message);      
            }
            IndicateEncodingIsFinished();
        }

        private void IndicateEncodingIsFinished()
        {
            if (OnEncodeCompleted != null)
                OnEncodeCompleted(this, null);
        }

        public void Resume()
        {
            if (job != null)
            {
                start = start.AddMilliseconds(DateTime.Now.Millisecond - pauseTime.Millisecond);
                job.ResumeEncode();
            }
        }

        public void Pause()
        {
            if (job != null)
            {
                job.PauseEncode();
                pauseTime = DateTime.Now;
            }
        }

        public void Cancel()
        {
            if (job != null)
            {
                cancelEncode = true;
                job.CancelEncode();
            }
        }

        void job_StartFileEncoding(object sender, StartFileEncodingEventArgs e)
        {
            
        }

        void job_EncodeProgress(object sender, EncodeProgressEventArgs e)
        {
            if (this.cancelEncode)
            {
                e.Cancel = true;
                return;
            }

            if (pass != e.CurrentPass)
            {
                start = DateTime.Now;
                pass = e.CurrentPass;
            }


            double ByteProcessTime = 0;
            double EstimatedTime = 0;

            TimeSpan timeSpan = DateTime.Now - start;
            if (timeSpan.TotalSeconds > 0)
            {
                ByteProcessTime = e.CurrentPosition.TotalSeconds / timeSpan.TotalSeconds;
                if (ByteProcessTime > 0)
                {
                    EstimatedTime = (e.CurrentItem.FileDuration.TotalSeconds - e.CurrentPosition.TotalSeconds) / ByteProcessTime;
                    timeSpan = TimeSpan.FromSeconds(EstimatedTime);

                    ProgressText = string.Format("pass {0} of {1}: {2:00}:{3:00}:{4:00}", e.CurrentPass, e.TotalPasses, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
                }
            }
            Progress = e.Progress;
            if (OnProgressChanged != null)
                OnProgressChanged(this, null);
        }
        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
